/*
 *  Copyright 2013 by Sebastian Gesemann
 *
 *  This file is part of a SipHash++, a C++ SipHash implementation.
 *
 *  SipHash is a family of pseudorandom functions (a.k.a. keyed hash
 *  functions) optimized for speed on short messages. It was designed
 *  by Jean-Philippe Aumasson and Daniel J. Bernstein. Check out
 *  https://131002.net/siphash/
 *
 *  SipHash++ is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  SipHash++ is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with SipHash++.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "siphash.hpp"

namespace siphash {


using std::uint_fast32_t;


/// takes 4 bytes and returns an integer (little endian)
inline uint_fast32_t bytes2int32(const unsigned char data[])
{
	return uint_fast32_t(data[0])
	     | uint_fast32_t(data[1]) << 8
	     | uint_fast32_t(data[2]) << 16
	     | uint_fast32_t(data[3]) << 24;
}

/// takes 8 bytes and returns an integer (little endian)
inline uint64_t bytes2int64(const unsigned char data[])
{
	return bytes2int32(data) | uint64_t(bytes2int32(data+4)) << 32;
}

/// takes 0 to 7 bytes and returns an integer (little endian)
inline uint64_t bytes2int(unsigned len, const unsigned char data[])
{
	assert(len <= 7);
	uint64_t r = 0;
	switch (len & 7) {
	case 7: r |= uint64_t(data[6]) << 48;
	case 6: r |= uint64_t(data[5]) << 40;
	case 5: r |= uint64_t(data[4]) << 32;
	case 4: r |= uint64_t(data[3]) << 24;
	case 3: r |= uint64_t(data[2]) << 16;
	case 2: r |= uint64_t(data[1]) <<  8;
	case 1: r |= uint64_t(data[0]);
	case 0: break;
	}
	return r;
}


template<int By>
struct rot {};

#define DEFINE_ROTATION(n) const rot<n> r##n = {}
DEFINE_ROTATION(13);
DEFINE_ROTATION(16);
DEFINE_ROTATION(17);
DEFINE_ROTATION(21);
DEFINE_ROTATION(32);
#undef DEFINE_ROTATION

/// in-place rotation
template<int By>
inline uint64_t& operator<<=(uint64_t & v, rot<By>)
{
	return v = (v << By) | (v >> (64-By));
}


key_t key(const unsigned char k[16])
{
	key_t result = {{
		bytes2int64(k),
		bytes2int64(k+8)
	}};
	return result;
}

state_t init()
{
	state_t v = {{
		0x736f6d6570736575ull,
		0x646f72616e646f6dull,
		0x6c7967656e657261ull,
		0x7465646279746573ull
	}};
	return v;
}

state_t init(key_t const& k)
{
	state_t v = init();
	v[0] ^= k[0];
	v[1] ^= k[1];
	v[2] ^= k[0];
	v[3] ^= k[1];
	return v;
}

inline void round(state_t & v)
{
	v[0] += v[1];   v[2] += v[3];
	v[1] <<= r13;   v[3] <<= r16;  // rotations
	v[1] ^= v[0];   v[3] ^= v[2];
	v[0] <<= r32;                  // rotation
	v[2] += v[1];   v[0] += v[3];
	v[1] <<= r17;   v[3] <<= r21;  // rotations
	v[1] ^= v[2];   v[3] ^= v[0];
	v[2] <<= r32;                  // rotation
}

void update2(state_t & v, uint64_t m)
{
	v[3] ^= m;
	round(v);
	round(v);
	v[0] ^= m;
}

uint64_t finish4(state_t & v)
{
	v[2] ^= 0xFF;
	round(v);
	round(v);
	round(v);
	round(v);
	return v[0]^v[1]^v[2]^v[3];
}

uint64_t atonce24(key_t const& k, std::size_t len, const unsigned char data[])
{
	state_t v = init(k);
	uint64_t b = uint64_t(len & 0xFF) << 56;
	while (len>=8) {
		uint64_t m = bytes2int64(data);
		update2(v,m);
		data += 8;
		len  -= 8;
	}
	b |= bytes2int(len,data);
	update2(v,b);
	return finish4(v);
}


stream24_t::stream24_t()
: v(init())
, b(0)
{}

stream24_t::stream24_t(key_t const& k)
: v(init(k))
, b(0)
{}

void stream24_t::reset()
{
	v = init();
	b = 0;
}

void stream24_t::reset(key_t const& k)
{
	v = init(k);
	b = 0;
}

void stream24_t::update(std::size_t len, const unsigned char data[])
{
	// accumulated lengths mod 256
	const unsigned acc_len_mod_256_old = (b >> 56) & 0xFF;
	const unsigned acc_len_mod_256_new = (acc_len_mod_256_old + len) & 0xFF;
	b &= 0x00FFFFFFFFFFFFFFull;
	if (const unsigned buff_fill = acc_len_mod_256_old & 7) {
		// we have at least one byte in the buffer
		unsigned chunk = 8-buff_fill; // let's try to fill it
		if (len<chunk) chunk = len;   // with what is available
		b |= bytes2int(chunk,data) << buff_fill*8;
		data += chunk;
		len  -= chunk;
		if (buff_fill + chunk == 8) { // is it full?
			update2(v,b);
			b = 0;
		}
	}
	b |= uint64_t(acc_len_mod_256_new) << 56; // store new counter
	if (len == 0) return;
	while (len>=8) {
		uint64_t m = bytes2int64(data);
		update2(v,m);
		data += 8;
		len  -= 8;
	}
	assert(len<8);
	b |= bytes2int(len,data);
}

uint64_t stream24_t::finish()
{
	update2(v,b);
	auto h = finish4(v);
	b = 0;
	return h;
}

} // namespace siphash
